Add wrap_entries processor to mutate-event-processors#6665
Conversation
Adds a new map_entries processor that wraps each element of a primitive
array into an object using a configured key name. This enables downstream
processors like add_entries and delete_entries with iterate_on, which
require List of Map and cannot operate on primitive arrays.
Example: ["alice", "bob"] -> [{"name": "alice"}, {"name": "bob"}]
Configuration options:
- source (required): key of the primitive array to transform
- target (optional): key to write result to (defaults to source)
- key (required): key name in each resulting object
- exclude_null_empty_values: filter out null/empty elements
- append_if_target_exists: append to existing target array
- map_entries_when: conditional expression
- tags_on_failure: tags on processing failure
Includes 21 unit tests and 3 config tests.
Signed-off-by: Nishant Kadivar <nimahesx@amazon.com>
|
|
||
| if (!event.containsKey(source)) { | ||
| LOG.warn(EVENT, "Source key [{}] does not exist in event [{}], skipping.", source, event); | ||
| addTagsOnFailure(event); |
There was a problem hiding this comment.
Is a missing source key really a failure? In a pipeline processing mixed event types, lots of events won't have this key — tagging all of them as failed seems noisy. The other mutate-event processors (SelectEntriesProcessor, FilterListProcessor) treat missing keys as a silent no-op. I'd drop the addTagsOnFailure here and make it a debug-level log instead, reserving failure tags for actual errors like "source exists but isn't a list."
| continue; | ||
| } | ||
| } | ||
| final Map<String, Object> wrapped = new HashMap<>(1); |
There was a problem hiding this comment.
Since this map is never modified after the put, Collections.singletonMap(key, element) would be a better fit — immutable, less memory, and communicates that it's a single-entry map.
| "for valid expression syntax", config.getMapEntriesWhen())); | ||
| } | ||
|
|
||
| if (config.getTagsOnFailure() != null) { |
There was a problem hiding this comment.
Nit: none of the other mutate-event processors validate tag contents like this (AddEntryProcessor, SelectEntriesProcessor, etc.). Not wrong, but it's inconsistent
There was a problem hiding this comment.
To be consistent with the other processor, I have removed tag content validation
| result.add(wrapped); | ||
| } | ||
|
|
||
| if (result.isEmpty()) { |
There was a problem hiding this comment.
When exclude_null_empty_values: true and all elements get filtered out, this early return leaves the original primitive array untouched. But if even one element
survives, the target gets overwritten with an object array. This means the same processor produces different output types depending on the data:
- Input [null, ""] → output stays [null, ""] (primitives)
- Input [null, "ok"] → output becomes [{"key": "ok"}] (objects)
Downstream processors like add_entries with iterate_on expect a consistent List — they'll break on the primitive case. I'd remove this early return and let it fall through to event.put(effectiveTarget, result) so the output is always [] when nothing survives filtering.
There was a problem hiding this comment.
I have removed the if (result.isEmpty()) { return; } check and let it fall through to event.put(effectiveTarget, result). That way the output is always List<Map<String, Object>> — either [] or [{"key": "val"}, ...] — regardless of whether filtering removed everything.
…ty filtered results - Change missing source key from warn+tags_on_failure to silent debug log, consistent with other mutate-event processors - Remove early return when all elements are filtered out by exclude_null_empty_values, ensuring consistent List<Map> output type - Update test and README to reflect new behavior Signed-off-by: Nishant Kadivar <nimahesx@amazon.com>
|
Looks good. Not sure about the name of the processor. Looks like it's doing list to list with each list item becoming a map? |
✅ License Header Check PassedAll newly added files have proper license headers. Great work! 🎉 |
Signed-off-by: Nishant Kadivar <nimahesx@amazon.com>
|
@kkondaka - The name map_entries was chosen because the processor maps each entry in the list into a key-value structure, aligning with the naming of other mutate-event processors. |
Regarding this violation, We do not have headers specifically defined in these YAML files, as we observed that they were not included in any of the existing processor files. This was done to maintain consistency across processors. Let me know if that needed to be included. |
I think this is a good point. "Map entries" sounds like it is running a map function. The word map can be confused with a verb. |
dlvenable
left a comment
There was a problem hiding this comment.
Thank you @nishantKadivar for this contribution. I have a few comments. We should change the name as well.
| return new Record<>(JacksonEvent.builder().withEventType("event").withData(data).build()); | ||
| } | ||
|
|
||
| // --- Constructor validation delegation --- |
There was a problem hiding this comment.
Let's avoid these comments for sections. Remove them or use @Nested classes.
| @@ -0,0 +1,10 @@ | |||
| test-pipeline: | |||
| continue; | ||
| } | ||
| } | ||
| result.add(Collections.singletonMap(key, element)); |
There was a problem hiding this comment.
I suspect that JacksonEvent will take this as-is and not remap. The singletonMap is immutable and thus would fail to mutate in downstream processors. Let's create a new map and put that.
The key verb you use in the README is "wrap." This processor name should start with |
|
|
||
| @Test | ||
| void doExecute_with_string_array_wraps_each_element_into_object_in_place() { | ||
| final Record<Event> record = createEvent(Map.of("names", Arrays.asList("alpha", "beta"))); |
There was a problem hiding this comment.
Let's add some test cases when the names are not primitives.
e.g.
List.of(Map.of("name": "alpha"), Map.of("name": "beta"))
and
List.of(List.of("alpha1": "beta1"), List.of("alpha2": "beta2"))
There was a problem hiding this comment.
I have added test cases for both the scenarios.
Signed-off-by: Nishant Kadivar <nimahesx@amazon.com>
| } | ||
|
|
||
| @Test | ||
| void doExecute_with_map_elements_wraps_each_map_into_object() { |
There was a problem hiding this comment.
Is this the desirable behavior? I'm ok to approve now, but we should follow up and discuss if we want to have this before we release 2.16.
There was a problem hiding this comment.
Hello @dlvenable ,
I have raised an issue for the discussion about the wrap_entries behaviour #6753.
Add
wrap_entriesprocessor to mutate-event-processorsDescription
Adds a new
wrap_entriesprocessor that wraps each element of a primitive array into an object using a configured key name. This enables downstream processors likeadd_entriesanddelete_entrieswithiterate_on, which requireList<Map<String, Object>>and cannot operate on primitive arrays (strings, numbers, booleans).For example,
["alice", "bob"]becomes[{"name": "alice"}, {"name": "bob"}].Issues Resolved
#6611
Configuration Options
sourcetargetsourcekeyexclude_null_empty_valuesfalseappend_if_target_existsfalsemap_entries_whennulltags_on_failurenullCheck List
--signoffEnd-to-End Test Results
Tested manually using Data Prepper's
filesource andfilesink.Pipeline Configuration
Input (5 records)
{"names": ["alice", "bob", "charlie"], "type": "users"} {"tags": ["critical", "network", "security"], "source": "firewall", "type": "tagged"} {"list_a": [{"url": "http://api.example.com/v1"}, {"url": "http://api.example.com/v2"}], "list_b": [{"url": "http://cdn.example.com/assets"}], "type": "urls"} {"items": ["laptop", "monitor", "keyboard", "mouse"], "department": "IT", "type": "inventory"} {"scores": [95, 82, 78, 91, 88], "subject": "math", "type": "grades"}Output (actual results)
{"names":[{"name":"alice"},{"name":"bob"},{"name":"charlie"}],"type":"users"} {"tags":[{"value":"critical"},{"value":"network"},{"value":"security"}],"source":"firewall","type":"tagged"} {"list_a":[{"url":"http://api.example.com/v1"},{"url":"http://api.example.com/v2"}],"list_b":[{"url":"http://cdn.example.com/assets"}],"type":"urls"} {"items":["laptop","monitor","keyboard","mouse"],"department":"IT","type":"inventory","inventory_items":[{"product":"laptop"},{"product":"monitor"},{"product":"keyboard"},{"product":"mouse"}]} {"scores":[{"score":95},{"score":82},{"score":78},{"score":91},{"score":88}],"subject":"math","type":"grades"}E2E Scenarios
type=users)type=tagged)type=urls)type=inventory)type=grades)